package pl.edu.mimuw.mas.editor;

import javax.media.opengl.GLCanvas;

import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.mimuw.mas.bezier.BezierSurface;
import pl.edu.mimuw.mas.bezier.ChartModeller;
import pl.edu.mimuw.mas.bezier.ControlPoints;
import pl.edu.mimuw.mas.chart.Area;
import pl.edu.mimuw.mas.chart.Chart;
import pl.edu.mimuw.mas.editor.listener.AboutMenuItemListener;
import pl.edu.mimuw.mas.editor.listener.ExitMenuItemListener;
import pl.edu.mimuw.mas.editor.listener.NewMenuItemListener;
import pl.edu.mimuw.mas.editor.listener.OpenMenuItemListener;
import pl.edu.mimuw.mas.editor.listener.SaveAsMenuItemListener;
import pl.edu.mimuw.mas.editor.listener.SaveMenuItemListener;
import pl.edu.mimuw.mas.editor.listener.ToggleFullScr3DMenuItemListener;
import pl.edu.mimuw.mas.editor.widget.ChartInfoWidget;
import pl.edu.mimuw.mas.editor.widget.ControlPointsWidget;
import pl.edu.mimuw.mas.editor.widget.JackWidget;
import pl.edu.mimuw.mas.editor.widget.SelectionWidget;
import pl.edu.mimuw.mas.utils.Branding;
import pl.edu.mimuw.mas.visualization.ChartEditorVisualization;

/**
 * Implementacja głównego okna edytora map.
 */
public class MainEditorWindow {
	
	private static final int X_SIZE = 800;
	private static final int Y_SIZE = 600;
	
	private final Logger log = LoggerFactory.getLogger(getClass());
	
	private Chart chart;
	private ControlPoints controlPoints;
	private Area selection;
	private String fileName = null;

	
	private ChartModeller modeller;
	
	private ChartEditorVisualization visualizer = null;
	private GLCanvas visualizationCanvas;
	
	protected Shell shell;
	private SashForm mainDivider;
	
	private ChartInfoWidget chartInfoWidget;
	private SelectionWidget selectionWidget;
	private ControlPointsWidget controlPointsWidget;
	private JackWidget jackWidget;
	
	private Composite leftComposite;
	private Composite rightComposite;
	
	private Menu menuBar;
	private MenuItem fileMenu, viewMenu, helpMenu;
	private Menu fileMenuList, viewMenuList, helpMenuList;
	private MenuItem newMenuItem, openMenuItem, saveMenuItem, saveAsMenuItem, exitMenuItem, toggleFullScr3DMenuItem, aboutMenuItem;
	
	/**
	 * Uruchamia aplikację.
	 * @param args argumenty uruchomienia są ignorowane
	 */
	public static void main(String[] args) {
		try {
			Branding.printLogo();
			MainEditorWindow window = new MainEditorWindow();
			window.open();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Otwiera główne okno aplikacji.
	 */
	public void open() {
		final Display display = Display.getDefault();
		createContents();
		shell.open();
		shell.layout();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
	}

	/**
	 * Tworzy zawartość okna (layouty, pola tekstowy, canvasy etc).
	 */
	protected void createContents() {
		shell = new Shell();
		shell.addShellListener(new ShellAdapter() {
			public void shellClosed(final ShellEvent arg0) {
				System.exit(0);
			}
		});
		shell.setLayout(new GridLayout());
		shell.setSize(X_SIZE, Y_SIZE);
		shell.setMaximized(true);
		shell.setText("DeerHunter - Edytor Map");

		mainDivider = new SashForm(shell, SWT.NONE);
		mainDivider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		leftComposite = new Composite(mainDivider, SWT.NONE);
		leftComposite.setLayout(new GridLayout());
		
		rightComposite = new Composite(mainDivider, SWT.EMBEDDED);
		
		visualizationCanvas = new GLCanvas();
		SWT_AWT.new_Frame(rightComposite);
		SWT_AWT.getFrame(rightComposite).add(visualizationCanvas);
		visualizationCanvas.setVisible(false);
		
		chartInfoWidget = new ChartInfoWidget(leftComposite, SWT.NONE);
		chartInfoWidget.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		chartInfoWidget.setVisible(false);
		
		selectionWidget = new SelectionWidget(leftComposite, SWT.NONE);
		selectionWidget.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		selectionWidget.setVisible(false);
		selectionWidget.addListener(SWT.Modify, new Listener() {
			@Override
			public void handleEvent(Event e) {
				setSelection((Area) e.data);
			}
		});
		
		controlPointsWidget = new ControlPointsWidget(leftComposite, SWT.NONE);
		controlPointsWidget.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		controlPointsWidget.setVisible(false);
		controlPointsWidget.addListener(SWT.Modify, new Listener() {
			@Override
			public void handleEvent(Event e) {
				setControlPoints((ControlPoints) e.data);
			}
		});
		
		jackWidget = new JackWidget(leftComposite, SWT.NONE);
		jackWidget.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		jackWidget.setVisible(false);
		jackWidget.addListener(SWT.Modify, new Listener() {
			@Override
			public void handleEvent(Event e) {
				shiftControlPoints((Float) e.data);
			}
		});
		
		menuBar = new Menu(shell, SWT.BAR);
		shell.setMenuBar(menuBar);
		
		fileMenu = new MenuItem(menuBar, SWT.CASCADE);
		fileMenu.setText("Plik");

		fileMenuList = new Menu(fileMenu);
		fileMenu.setMenu(fileMenuList);

		newMenuItem = new MenuItem(fileMenuList, SWT.NONE);
		newMenuItem.setAccelerator(SWT.CTRL | 'n');
		newMenuItem.addSelectionListener(new NewMenuItemListener(shell, this));
		newMenuItem.setText("&Nowa mapa\tCtrl+N");

		openMenuItem = new MenuItem(fileMenuList, SWT.NONE);
		openMenuItem.setAccelerator(SWT.CTRL | 'o');
		openMenuItem.addSelectionListener(new OpenMenuItemListener(shell, this));
		openMenuItem.setText("&Otwórz\tCtrl+O");

		new MenuItem(fileMenuList, SWT.SEPARATOR);

		saveMenuItem = new MenuItem(fileMenuList, SWT.NONE);
		saveMenuItem.setAccelerator(SWT.CTRL | 's');
		saveMenuItem.addSelectionListener(new SaveMenuItemListener(this));
		saveMenuItem.setEnabled(false);
		saveMenuItem.setText("Zapisz\tCtrl+S");

		saveAsMenuItem = new MenuItem(fileMenuList, SWT.NONE);
		saveAsMenuItem.addSelectionListener(new SaveAsMenuItemListener(shell, this));
		saveAsMenuItem.setEnabled(false);
		saveAsMenuItem.setText("Zapisz jako...");
		
		new MenuItem(fileMenuList, SWT.SEPARATOR);

		exitMenuItem = new MenuItem(fileMenuList, SWT.NONE);
		exitMenuItem.setAccelerator(SWT.CTRL | 'x');
		exitMenuItem.addSelectionListener(new ExitMenuItemListener());
		exitMenuItem.setText("Wyjście\tCtrl+X");

		viewMenu = new MenuItem(menuBar, SWT.CASCADE);
		viewMenu.setText("Widok");
		
		viewMenuList = new Menu(viewMenu);
		viewMenu.setMenu(viewMenuList);
		
		toggleFullScr3DMenuItem = new MenuItem(viewMenuList, SWT.NONE);
		toggleFullScr3DMenuItem.setAccelerator(SWT.CTRL | 'f');
		toggleFullScr3DMenuItem.addSelectionListener(new ToggleFullScr3DMenuItemListener(this, false));
		
		
		helpMenu = new MenuItem(menuBar, SWT.CASCADE);
		helpMenu.setText("Pomoc");
		
		helpMenuList = new Menu(helpMenu);
		helpMenu.setMenu(helpMenuList);
		
		aboutMenuItem = new MenuItem(helpMenuList, SWT.NONE);
		aboutMenuItem.addSelectionListener(new AboutMenuItemListener(shell));
		aboutMenuItem.setText("O programie...");

		mainDivider.setWeights(new int[] {166, 311 });
	}
	
	/**
	 * Zwraca nazwę pliku, w którym zapisane są informację aktualnie
	 * edytowanej mapy. W przypadku niezapisanego obiektu na dysk 
	 * zwracany jest null.
	 * @return nazwa pliku
	 */
	public String getFileName() {
		return fileName;
	}

	/**
	 * Ustawia nazwę pliku do zapisywania danych.
	 * @param fileName nazwa pliku
	 */
	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
	
	/**
	 * Zwraca aktualnie edytowaną mapę.
	 * @return obiekt klasy {@link pl.edu.mimuw.mas.chart.Chart Chart} edytowanej mapy
	 */
	public Chart getChart() {
		return chart;
	}
	
	/**
	 * Ustawia mape do edycji. Resetuje podglady mapy, informacje etc.
	 * @param chart nowa mapa
	 */
	public void setChart(Chart chart) {
		int columns = chart.getColumnsCount();
		int rows = chart.getRowsCount();
		float distance = chart.getDistance();
		log.info("Ustawiam nową mapę z  " 
				+ columns + " kolumnami, " 
				+ rows + " wierszami i dystansem " + distance);
		this.chart = chart;
		
		log.debug("Aktualizuję visualizer'a");
		visualizer = setViewer(visualizer, visualizationCanvas, rightComposite, chart);
				
		log.debug("Aktualizuję widgety kontrolne");
		chartInfoWidget.setChart(chart);
		if (!chartInfoWidget.isVisible()) {
			chartInfoWidget.setVisible(true);
		} else {
			chartInfoWidget.redraw();
		}
		
		selectionWidget.setChart(chart);
		if (!selectionWidget.isVisible()) {
			selectionWidget.setVisible(true);
		} else {
			selectionWidget.redraw();
		}
		
		controlPointsWidget.setVisible(true);
	}
	
	/**
	 * Zwraca siatke punktow kontrolnych stworzonych na danej mapie.
	 * @return punkty kontrolne({@link pl.edu.mimuw.mas.bezier.ControlPoints ControlPoints})
	 */
	public ControlPoints getControlPoints() {
		return controlPoints;
	}
	
	/**
	 * Ustawia nową siatkę punktów kontrolnych. Resetuje podglądy etc.  
	 * @param cp nowa siatka punktów kontrolnych
	 */
	public void setControlPoints(ControlPoints cp) {
		this.controlPoints = cp;
		
		try {
			visualizer.replaceContext(cp);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		jackWidget.setVisible(true);
		jackWidget.setControlPoints(cp);
		
		modeller = new ChartModeller(chart, new BezierSurface(cp, chart));
	}
	
	public ChartEditorVisualization setViewer(ChartEditorVisualization v, GLCanvas canvas, Composite composite, Chart chart) {
		ChartEditorVisualization ret;
		if (v != null) {
			v.setChart(chart);
			ret = v;
		} else {
			ret = new ChartEditorVisualization(canvas, chart);
		}
		canvas.setVisible(true);
		SWT_AWT.getFrame(composite).setVisible(true);
		return ret;
	}
	
	/**
	 * Zwraca wybrany obszar na mapie aktualnie edytowany.
	 * @return obszar edytowany ({@link pl.edu.mimuw.mas.chart.Area Area})
	 */
	public Area getSelection() {
		return selection;
	}
	
	/**
	 * Ustawia nowy obszar na mapie do edycji.
	 * @param a nowy obszar do edycji
	 */
	public void setSelection(Area a) {
		this.selection = a;
		
		controlPointsWidget.setSelection(a);
		jackWidget.setVisible(false);
		
		try {
			visualizer.replaceContext(a);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Włącza możliwość zapisywania mapy do wybranego pliku
	 * (udostępnia opcję  w menu aplikacji).
	 */
	public void enableSavingAs() {
		saveAsMenuItem.setEnabled(true);
	}
	
	/**
	 * Wyłącza możliwość zapisywania mapy do wybranego pliku
	 * (wyłącza opcję  w menu aplikacji).
	 */
	public void disableSavingAs() {
		saveAsMenuItem.setEnabled(false);
	}
	
	/**
	 * Włącza zapisywanie do bieżącego pliku (włącza
	 * opcję w menu aplikacji).
	 */
	public void enableSaving() {
		saveMenuItem.setEnabled(true);
	}
	
	/**
	 * Wyłącza zapisywanie do bieżącego pliku (wyłącza
	 * opcję w menu aplikacji).
	 */
	public void disableSaving() {
		saveMenuItem.setEnabled(false);
	}
	
	/**
	 * Przesuwa wybrane punkty kontrolne z biężącej siątki o shift
	 * w górę (jeśli dodatnie) lub w dół (jeśli ujemne).
	 * @param shift wartość przesunięcia
	 */
	public void shiftControlPoints(float shift) {
		log.info("Przesuwam punkty kontrolne o " + shift);
		controlPoints.shiftSelectedHeight(shift);
		modeller.model(selection);
	}
	
	/**
	 * Włącza / wyłącza wizualizacje 3D w całym oknie aplikacji.
	 * @param flag
	 */
	public void maximize3D(boolean flag) {
		if (flag) {
			mainDivider.setMaximizedControl(rightComposite);
			toggleFullScr3DMenuItem.setText("Wyłącz wizualizację w całym oknie");
		} else {
			mainDivider.setMaximizedControl(null);
			toggleFullScr3DMenuItem.setText("Włącz wizualizację w całym oknie");
		}
	}
}
